/**********************************************************************\

 Copyright 2012 Diggory Blake

 This file is part of klip-engine

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

\**********************************************************************/
#pragma once

#define NOMINMAX

#include <d3d9.h>
#include <d3dx9.h>
#include <Windows.h>
#include <vector>
#include <set>
#include <Cg/cg.h>
#include <Cg/cgD3D9.h>

#undef DrawState

#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
#pragma comment(lib, "cg.lib")
#pragma comment(lib, "cgD3D9.lib")

class kGraphicsDX;
class kImageDX;
class kView;
class kSprite;
class kSystemWindows;
class kEffect;
class kMaterial;
class kWindow;

#include "kgraphics.h"
#include "kewindowoptions.h"
#include "kblend.h"
#include "kerror.h"
#include "kstructdata.h"

class kGraphicsDX : public kGraphics {
	friend class kImageDX;
private:
	struct DrawState {
	public:
		const kMaterial* material;
		kBlend blend;
		D3DPRIMITIVETYPE primType;
		DWORD fvf;
		union {
			uint32_t textureState;
			struct {
				uint32_t repeatX	: 1;
				uint32_t repeatY	: 1;
			};
		};

		inline bool operator==(const DrawState& other) {
			return (material == other.material)
				&& (primType == other.primType)
				&& (fvf == other.fvf)
				&& (textureState == other.textureState)
				&& (blend == other.blend);
		}
		inline bool operator!=(const DrawState& other) {
			return (material != other.material)
				|| (primType != other.primType)
				|| (fvf != other.fvf)
				|| (textureState != other.textureState)
				|| (blend != other.blend);
		}
	};
	struct DrawBuffer {
		kAABB aabb;
		DrawState state;
		kStructDataBuffer vertexData;
		size_t vertexCount;
	};
	enum ESemantic {
		ES_View = 0,
		ES_Projection,
		ES_ViewProjection,
		ES_InvView,
		ES_InvProjection,
		ES_InvViewProjection,
		ES_Count
	};
	static const tchar* _semanticNames[ES_Count];
	inline D3DBLEND convertBlendFactor(keBlendFactor factor) {
		switch (factor) {
		case keBlend_Zero: return D3DBLEND_ZERO;
		case keBlend_One: return D3DBLEND_ONE;
		case keBlend_Src: return D3DBLEND_SRCCOLOR;
		case keBlend_InvSrc: return D3DBLEND_INVSRCCOLOR;
		case keBlend_Dst: return D3DBLEND_DESTCOLOR;
		case keBlend_InvDst: return D3DBLEND_INVDESTCOLOR;
		case keBlend_SrcAlpha: return D3DBLEND_SRCALPHA;
		case keBlend_InvSrcAlpha: return D3DBLEND_INVSRCALPHA;
		case keBlend_DstAlpha: return D3DBLEND_DESTALPHA;
		case keBlend_InvDstAlpha: return D3DBLEND_INVDESTALPHA;
		case keBlend_SrcAlphaSaturate: return D3DBLEND_SRCALPHASAT;
		default: throw kError(_T("Invalid blend factor!"));
		}
	}
	inline D3DBLENDOP convertBlendOp(keBlendOp op) {
		switch (op) {
		case keBlend_Add: return D3DBLENDOP_ADD;
		case keBlend_Subtract: return D3DBLENDOP_SUBTRACT;
		case keBlend_RevSubtract: return D3DBLENDOP_REVSUBTRACT;
		case keBlend_Min: return D3DBLENDOP_MIN;
		case keBlend_Max: return D3DBLENDOP_MAX;
		default: throw kError(_T("Invalid blend op!"));
		}
	}

	kSystemWindows* _system;
	kWindow* _window;
	IDirect3D9* _direct3d;
	IDirect3DDevice9* _device;
	kImageDX* _backBuffer;
	std::vector<DrawBuffer*> _drawBuffers;
	kVec2i _size;
	keWindowOptions _options;
	std::set<kImageDX*> _images;
	CGcontext _cgContext;
	D3DXMATRIX _semanticValues[ES_Count];

	// Callback handles
	kHandle _resizeCallback;
	kHandle _changeCallback;
	kHandle _deleteCallback;

	void flush(unsigned bufferIndex);
	void drawBuffer(const DrawBuffer& buffer);
	void fillPresentParameters(D3DPRESENT_PARAMETERS* pp);
	void resetDevice();
	void createImageDX(const kVec2i& size, keImageOptions options, IDirect3DTexture9** texture, IDirect3DSurface9** surface);

protected:
	virtual void onUpdate(float delta);

public:
	using kGraphics::createImage;
	using kGraphics::createEffect;

	kGraphicsDX(kSystemWindows* system, kWindow* window);
	
	virtual kImage* getBackBuffer();
	virtual kImage* createImage(kStream* source, keImageOptions options);
	virtual kImage* createImage(const kVec2i& size, const kColor& color, keImageOptions options);
	virtual void deleteImage(kImage* image);
	virtual void beginRender(kView* view, bool resume = false);
	virtual void endRender();
	virtual void renderMesh(kMesh2DBase* mesh);
	virtual kWindow* getWindow();
	virtual kEffect* createEffect(kStream* source);
	virtual void deleteEffect(kEffect* effect);
	virtual void flush(const kMaterial* material = 0);

	void getMatrixDX(const kMat3x3& mat, D3DXMATRIX* matdx);

	~kGraphicsDX();
};
